Skip to main content

Canvas 2D

Canvas2D is an optional package used to bridge HTML Canvas API with Wolfram Language allowing low-level raster realtime render.

Setting up

Firstly, you need to load this package into any context except Global:

Needs["Canvas2D`"->"ctx`"];

To create canvas context use the following constructor:

context = ctx`Canvas2D[]

Then to display and render graphics in the given context - provide Image:

Image[context, ImageResolution->{300,300}]

Draw basic shapes

Created context is used to pipe commands to Canvas API (and then internally to GPU). Let's make basic shapes:

ctx`BeginPath[context];
ctx`MoveTo[context, {25, 25}];
ctx`LineTo[context, {105, 25}];
ctx`LineTo[context, {25, 105}];
ctx`Fill[context];

ctx`BeginPath[context];
ctx`MoveTo[context, {125, 125}];
ctx`LineTo[context, {125, 45}];
ctx`LineTo[context, {45, 125}];
ctx`ClosePath[context];
ctx`Stroke[context];

(* Send the buffer of commands *)
ctx`Dispatch[context];

API allows to make gradients and change filling colors as well:

(* top-left quarter *)
ctx`SetFillStyle[context, "#FD0"];
ctx`FillRect[context, {0, 0}, 2{75, 75}];

(* top-right quarter *)
ctx`SetFillStyle[context, "#6C0"];
ctx`FillRect[context, 2{75, 0}, 2{75, 75}];

(* bottom-left quarter *)
ctx`SetFillStyle[context, "#09F"];
ctx`FillRect[context, 2{0, 75}, 2{75, 75}];

(* bottom-right quarter *)
ctx`SetFillStyle[context, "#F30"];
ctx`FillRect[context, 2{75, 75}, 2{75, 75}];

ctx`SetFillStyle[context, "#FFF"];

ctx`SetGlobalAlpha[context, 0.2];

Do[
ctx`BeginPath[context];
ctx`Arc[context, 2{75, 75}, 2 10 + 2 10 i, 0, 2.0 π];
ctx`Fill[context];
, {i, 0, 6}];

ctx`Dispatch[context];

Animation

Passing Image to EventHandler expression allows to capture user events: for example mouse tracking feature.

AnimationFrameListener is also extended to work with canvas context symbols, i.e:

AnimationFrameListener[context_, "Event"->event_String]

Let's make some complex animations!

Handler generator function

SetAttributes[handler, HoldRest]

handler[c_, tcursor_, {width_ height_}] := Module[
{
w = width, h = height, (* initial canvas size *)
n = 101,
cursor = tcursor,
(* number of particles *)
rotSpeed = 0.02, (* angular velocity *)
particles,
theta, rad, col, old
},

(* center point *)
cx = w/2;
cy = h/2;

(* semi-transparent drawing *)
ctx`SetGlobalAlpha[c, 0.5];

(* initialize particles: random angle, radius, colour *)
particles = Table[
{
RandomReal[{0, 2 π}],
RandomReal[{0, 150}],
ctx`ColorToString[RandomColor[]],
cursor
},
{n}
];

Function[Null,
cursor = cursor + 0.1 (tcursor - cursor);

ctx`SetFillStyle[c, {Black, Opacity[0.05]}];
ctx`FillRect[c, {0, 0}, {w, h}];

(* draw each particle: from last cursor pos to new one *)
Do[
particles[[i, 1]] += rotSpeed;
{theta, rad, col, old} = particles[[i]];
Module[{newPos},
newPos = cursor + {Cos[theta], Sin[theta]} rad;

ctx`BeginPath[c];
ctx`SetLineWidth[c, 4];
ctx`SetStrokeStyle[c, col];
ctx`MoveTo[c, old];
ctx`LineTo[c, newPos];
ctx`Stroke[c];

particles[[i,4]] = newPos;
],
{i, n}
];
ctx`Dispatch[c];
]
]


aContext = ctx`Canvas2D[];
tcursor = {250,250};

EventHandler["frame", handler[aContext, tcursor, {500,500}]];
EventHandler[
Image[aContext, ImageResolution->{500,500}, Epilog->{
AnimationFrameListener[aContext, "Event"->"frame"]
}], {
"mousemove" -> Function[xy,
tcursor = {xy[[1]], xy[[2]]}
]
}
]